import 'dart:collection';
import 'dart:math';

import 'package:floor/floor.dart';
import 'package:share_fit/app/helper/ext/tr_ext.dart';
import 'package:share_fit/app/models/base_model.dart';
import 'package:share_fit/app/tools/date_tool.dart';
import 'package:share_fit/generated/locales.g.dart';

@Entity(tableName: 'menstrual')
class MenstrualModel extends BaseModel {
  /// 记录是否经期开始
  bool menstruating;

  /// 经期天数
  @ColumnInfo(name: 'menstruation_days')
  int? menstruationDays;

  /// 周期天数
  @ColumnInfo(name: 'period_days')
  int? periodDays;

  /// 经期开始时间
  @ColumnInfo(name: 'menstruation_start')
  int? menstruationStart;

  /// 经期结束时间
  @ColumnInfo(name: 'menstruation_end')
  int? menstruationEnd;

  /// 预测经期开始时间，上次经期开始日 + 经期周期
  @ColumnInfo(name: 'prediction_start')
  int? predictionStart;

  /// 预测经期结束时间
  @ColumnInfo(name: 'prediction_end')
  int? predictionEnd;

  /// 易孕开始时间，排卵日前5天
  @ColumnInfo(name: 'easy_pregnancy_start')
  int? easyPregnancyStart;

  /// 易孕结束时间，排卵日后4天
  @ColumnInfo(name: 'easy_pregnancy_end')
  int? easyPregnancyEnd;

  /// 排卵日，下次月经前的第14天
  @ColumnInfo(name: 'ovulation_date')
  int? ovulationDate;

  MenstrualModel(
    this.menstruating, {
    this.menstruationDays,
    this.periodDays,
    this.menstruationStart,
    this.menstruationEnd,
    this.predictionStart,
    this.predictionEnd,
    this.easyPregnancyStart,
    this.easyPregnancyEnd,
    this.ovulationDate,
    super.address,
    super.dateCreated,
    super.dateModified,
  });

  @override
  List<Object?> get props => [
        menstruating,
        menstruationDays,
        periodDays,
        menstruationStart,
        menstruationEnd,
        predictionStart,
        predictionEnd,
        easyPregnancyStart,
        easyPregnancyEnd,
        ovulationDate,
      ];

  @override
  bool? get stringify => true;

  @override
  bool from(Map map) {
    menstruating = map['menstruating'];
    menstruationDays = map['menstruationDays'] ?? 5;
    periodDays = map['periodDays'] ?? 28;
    menstruationStart = map['menstruationStart'] ?? '';
    menstruationEnd = map['menstruationEnd'] ?? '';
    predictionStart = map['predictionStart'] ?? '';
    predictionEnd = map['predictionEnd'] ?? '';
    easyPregnancyStart = map['easyPregnancyStart'] ?? '';
    easyPregnancyEnd = map['easyPregnancyEnd'] ?? '';
    ovulationDate = map['ovulationDate'] ?? '';
    return true;
  }

  @override
  toMap() {
    return {
      'mMenstrualStartTime': DateTool.getTimeInSeconds(DateTime.fromMillisecondsSinceEpoch(menstruationStart!)), // 生理周期开始时间，格林尼治时间，单位：秒
      'mMenstrualEndTime': DateTool.getTimeInSeconds(DateTime.fromMillisecondsSinceEpoch(menstruationEnd!)), // 生理周期结束时间，格林尼治时间，单位：秒
      'mEasyToPregnantStartTime': DateTool.getTimeInSeconds(DateTime.fromMillisecondsSinceEpoch(easyPregnancyStart!)), // 易孕期开始时间，格林尼治时间，单位：秒
      'mEasyToPregnantEndTime': DateTool.getTimeInSeconds(DateTime.fromMillisecondsSinceEpoch(easyPregnancyEnd!)), // 易孕期结束时间，格林尼治时间，单位：秒
      'mCycleDays': periodDays, // 生理周期的长度
      'mIsManual': 1, // 生理周期的长度
    };
  }

  void newModel({required DateTime startDate, int? inPeriodDays, int? inMenstruationDays, String? inAddress}) {
    // 周期天数
    int cycleDays = inPeriodDays ?? periodDays!;
    // 经期天数
    int menstrualDays = inMenstruationDays ?? menstruationDays!;
    // 经期开始日期
    DateTime mStartDate = startDate;
    // 经期结束日期
    DateTime mEndTime = DateTool.offsetDays(mStartDate, menstrualDays - 1);
    // 预测经期开始时间，上次经期开始日 + 经期周期
    DateTime predictionStartDate = DateTool.offsetDays(mStartDate, cycleDays);
    // 预测经期结束时间
    DateTime predictionEnd1 = DateTool.offsetDays(predictionStartDate, menstrualDays - 1);

    // 排卵日，下次月经前的第14天
    DateTime ovDate = DateTool.offsetDays(predictionStartDate, -14);
    // 易孕开始日期，排卵日前5天
    DateTime epStartTime = DateTool.offsetDays(ovDate, -5);
    // 易孕结束日期，排卵日后4天
    DateTime epEndTime = DateTool.offsetDays(ovDate, 4);

    periodDays = cycleDays;
    menstruationDays = menstrualDays;
    menstruationStart = mStartDate.millisecondsSinceEpoch;
    menstruationEnd = mEndTime.millisecondsSinceEpoch;
    predictionStart = predictionStartDate.millisecondsSinceEpoch;
    predictionEnd = predictionEnd1.millisecondsSinceEpoch;
    easyPregnancyStart = epStartTime.millisecondsSinceEpoch;
    easyPregnancyEnd = epEndTime.millisecondsSinceEpoch;
    ovulationDate = ovDate.millisecondsSinceEpoch;
    dateCreated = DateTime.now().millisecondsSinceEpoch;
    dateModified = DateTime.now().millisecondsSinceEpoch;
    address = inAddress;
  }

  inPredictionRange(DateTime now) {
    int ms = now.millisecondsSinceEpoch;
    return ms <= predictionEnd! && ms >= predictionStart!;
  }

  inMenstrualRange(DateTime now) {
    int ms = now.millisecondsSinceEpoch;
    return ms <= menstruationEnd! && ms >= menstruationStart!;
  }

  inOvulationRange(DateTime now) {
    return now.millisecondsSinceEpoch == ovulationDate!;
  }

  Map<DateTime, List<MenstrualEvent>> toPredictionEvent({int? dotColor}) {
    Map<DateTime, List<MenstrualEvent>> map = {};
    DateTime pe = DateTime.fromMillisecondsSinceEpoch(predictionEnd!);
    DateTime ps = DateTime.fromMillisecondsSinceEpoch(predictionStart!);
    int days = pe.difference(ps).inDays + 1;
    List.generate(days, (index) => map.putIfAbsent(ps.add(Duration(days: index)), () => [MenstrualEvent('', dotColor ?? 0xFFFFD740)]));
    return map;
  }

  /// 排卵日一般是在下次月经来潮的前14天，排卵日的前5天和后4天，连同排卵日在内共10天
  Map<DateTime, List<MenstrualEvent>> toOvulationEvent() {
    Map<DateTime, List<MenstrualEvent>> map = {};
    DateTime od = DateTime.fromMillisecondsSinceEpoch(ovulationDate!);
    DateTime ods = od.add(const Duration(days: -5));
    DateTime ode = od.add(const Duration(days: 4));
    int days = ode.difference(ods).inDays + 1;
    List.generate(days, (index) => map.putIfAbsent(ods.add(Duration(days: index)), () => [MenstrualEvent('', 0xFF4CAF50)]));
    return map;
  }

  Map<DateTime, List<MenstrualEvent>> toMenstruatingEvent({int? dotColor}) {
    Map<DateTime, List<MenstrualEvent>> map = {};
    DateTime me = DateTime.fromMillisecondsSinceEpoch(menstruationEnd!);
    DateTime ms = DateTime.fromMillisecondsSinceEpoch(menstruationStart!);
    int days = me.difference(ms).inDays + 1;
    List.generate(
        days > 0 ? days : days.abs(), (index) => map.putIfAbsent(ms.add(Duration(days: index)), () => [MenstrualEvent('', dotColor ?? 0xFFF44336)]));
    return map;
  }

  getEventMap(DateTime today) {
    SplayTreeMap<DateTime, List<MenstrualEvent>>? map = SplayTreeMap();
    if (inPredictionRange(today)) {
      map.addAll(toPredictionEvent(dotColor: 0xFFF44336));
    } else if (inMenstrualRange(today)) {
      map.addAll(toMenstruatingEvent());
    } else if (inOvulationRange(today)) {
      map.addAll(toOvulationEvent());
      map[DateTool.offsetDays(today, -2)] = map[today]!;
      map[DateTool.offsetDays(today, -1)] = map[today]!;
      map[DateTool.offsetDays(today, 1)] = map[today]!;
      map[DateTool.offsetDays(today, 2)] = map[today]!;
    } else {
      DateTime s = DateTime.fromMillisecondsSinceEpoch(menstruationStart!);
      DateTime p = DateTime.fromMillisecondsSinceEpoch(predictionStart!);
      DateTime o = DateTime.fromMillisecondsSinceEpoch(ovulationDate!);

      int menDays = DateTool.differentInDays(s, today);
      int preDays = DateTool.differentInDays(p, today);
      int ovDays = DateTool.differentInDays(o, today);
      if (menDays > 0 && menDays < preDays) {
        getNextEvent(map, today, s, (t) => t.compareTo(s) >= 0 ? 0xFFF44336 : 0xFFD5D1D1);
      } else if (preDays > 0 && preDays < menDays && preDays < ovDays) {
        getNextEvent(map, today, p, (t) => t.compareTo(p) >= 0 ? 0xFFF44336 : 0xFFD5D1D1);
      } else if (ovDays > 0 && ovDays < preDays) {
        getNextEvent(map, today, o, (t) => t.compareTo(o) == 0 ? 0xFF4CAF50 : 0xFFD5D1D1);
      } else if (preDays > menDays && preDays > ovDays) {
        getNextEvent(map, today, p, (t) => t.compareTo(p) >= 0 ? 0xFFF44336 : 0xFFD5D1D1);
      }
    }
    return map;
  }

  getNextEvent(SplayTreeMap<DateTime, List<MenstrualEvent>> map, DateTime today, DateTime s, Function getColor) {
    for (int i = 1; i <= menstruationDays!; i++) {
      map.putIfAbsent(today, () => [MenstrualEvent('', getColor(today))]);
      today = today.add(const Duration(days: 1));
    }
  }

  /// 首页小组件
  get menstrualText {
    String text = LocaleKeys.no_records_text.ts;
    if (menstruating && menstruationStart != null && menstruationStart! > 0) {
      DateTime today = DateTool.truncateHMS();
      DateTime s = DateTime.fromMillisecondsSinceEpoch(menstruationStart!);
      DateTime p = DateTime.fromMillisecondsSinceEpoch(predictionStart!);
      DateTime o = DateTime.fromMillisecondsSinceEpoch(ovulationDate!);

      if (inMenstrualRange(today) || inPredictionRange(today)) {
        int days = inMenstrualRange(today) ? DateTool.differentInDays(today, s) + 1 : DateTool.differentInDays(today, p) + 1;
        return LocaleKeys.cycle_calendar_tip_text.ts.replaceAll(RegExp(r'\d+'), days.toString());
      } else if (inOvulationRange(today)) {
        text = LocaleKeys.cycle_calendar_ovulation_text.ts;
      } else {
        int menDays = DateTool.differentInDays(s, today);
        int preDays = DateTool.differentInDays(p, today);
        int ovDays = DateTool.differentInDays(o, today);
        if (menDays > 0 && menDays < preDays) {
          text = LocaleKeys.cycle_calendar_menstrual_next_time_text.ts.replaceAll(RegExp(r'\d+'), menDays.abs().toString());
        } else if (preDays > 0 && preDays < menDays && preDays < ovDays) {
          text = LocaleKeys.cycle_calendar_menstrual_next_time_text.ts.replaceAll(RegExp(r'\d+'), preDays.abs().toString());
        } else if (ovDays > 0 && ovDays < preDays) {
          text = LocaleKeys.cycle_calendar_ovulation_next_time_text.ts.replaceAll(RegExp(r'\d+'), ovDays.abs().toString());
        } else if (preDays > menDays && preDays > ovDays) {
          text = LocaleKeys.cycle_calendar_menstrual_next_time_text.ts.replaceAll(RegExp(r'\d+'), preDays.abs().toString());
        } else {
          text = '';
        }
      }
    }
    return text;
  }
}

class MenstrualEvent {
  String title;
  int color;

  MenstrualEvent(this.title, this.color);
}
